package cn.tannn.ops.customer.controller;

import cn.tannn.jdevelops.annotations.web.authentication.ApiMapping;
import cn.tannn.jdevelops.annotations.web.authentication.ApiPlatform;
import cn.tannn.jdevelops.annotations.web.constant.PlatformConstant;
import cn.tannn.jdevelops.annotations.web.mapping.PathRestController;
import cn.tannn.jdevelops.exception.built.UserException;
import cn.tannn.jdevelops.jpa.constant.SQLOperator;
import cn.tannn.jdevelops.jpa.request.Sorteds;
import cn.tannn.jdevelops.jpa.result.JpaPageResult;
import cn.tannn.jdevelops.jwt.redis.service.RedisLoginService;
import cn.tannn.jdevelops.jwt.standalone.util.JwtWebUtil;
import cn.tannn.jdevelops.jwt.standalone.util.UserUtil;
import cn.tannn.jdevelops.result.bean.SerializableBean;
import cn.tannn.jdevelops.result.response.ResultPageVO;
import cn.tannn.jdevelops.result.response.ResultVO;
import cn.tannn.jdevelops.result.utils.ListTo;
import cn.tannn.jdevelops.utils.jwt.constant.UserStatusMark;
import cn.tannn.jdevelops.utils.jwt.module.LoginJwtExtendInfo;
import cn.tannn.ops.customer.constant.CustomerStatus;
import cn.tannn.ops.customer.constant.RoleDef;
import cn.tannn.ops.customer.constant.UserLoginNameDef;
import cn.tannn.ops.customer.controller.dto.*;
import cn.tannn.ops.customer.controller.vo.CustomerVO;
import cn.tannn.ops.customer.entity.Customer;
import cn.tannn.ops.customer.entity.Role;
import cn.tannn.ops.customer.entity.RoleCustomer;
import cn.tannn.ops.customer.service.CustomerService;
import cn.tannn.ops.customer.service.RoleCustomerService;
import cn.tannn.ops.customer.service.RoleService;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.extensions.Extension;
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import static cn.tannn.jdevelops.jwt.standalone.exception.PermissionsCode.AUTH_ERROR;
import static cn.tannn.jdevelops.utils.jwt.exception.UserCode.USER_EXIST_ERROR;


/**
 * 用户管理
 *
 * @author <a href="https://tannn.cn/">tan</a>
 * @date 2023/8/28 14:06
 */
@Tag(name = "用户管理", description = "用户管理",
        extensions = {
                @Extension(properties = {@ExtensionProperty(name = "x-order", value = "3", parseValue = true)})}
)
@PathRestController("user")
@Slf4j
@Validated
public class CustomerController {

    @Value("${user.default-password}")
    private String userDefPassword;

    private final CustomerService customerService;
    private final RedisLoginService redisLoginService;
    private final RoleCustomerService roleCustomerService;
    private final RoleService roleService;

    public CustomerController(RedisLoginService redisLoginService,
                              CustomerService customerService
            , RoleCustomerService roleCustomerService
            , RoleService roleService) {
        this.customerService = customerService;
        this.redisLoginService = redisLoginService;
        this.roleCustomerService = roleCustomerService;
        this.roleService = roleService;
    }

    /**
     * 修改密码 - 旧密码修改
     */
    @ApiMapping(value = "/fix/password", checkToken = false, method = RequestMethod.POST)
    @Operation(summary = "修改密码")
    @Transactional(rollbackFor = Exception.class)
    public ResultVO<String> fixPassword(@RequestBody @Valid PasswordEdit password, HttpServletRequest request) {
        // 被修改的账户有效性验证
        Customer user = customerService.findOnly("loginName", password.getLoginName())
                .orElseThrow(() -> new UserException(USER_EXIST_ERROR));
        // 没有旧密码那就是管理员更改密码
        if (StringUtils.isEmpty(password.getOldPassword())) {
            // 非 admin 角色用户操作
            throw new UserException(AUTH_ERROR.getCode(), "遗忘密码请联系管理员辅助更改");
        } else {
            // 非admin 修改 验证旧密码
            user.verifyUserPassMd5(password.getOldPassword());
        }

        customerService.editPassword(password.getLoginName(), password.getNewPassword());
        redisLoginService.loginOut(user.getLoginName());
        return ResultVO.successMessage("密码修改成功，请妥善保管新密码");
    }

    /**
     * 重置密码 - 只能管理员操作
     */
    @ApiMapping(value = "/reset/password", checkToken = false, method = RequestMethod.POST)
    @Operation(summary = "重置密码 - 只能管理员操作")
    @Transactional(rollbackFor = Exception.class)
    public ResultVO<String> resetPassword(@RequestBody @Valid PasswordReset password, HttpServletRequest request) {
        // 被修改的账户有效性验证
        Customer user = customerService.findOnly("loginName", password.getLoginName())
                .orElseThrow(() -> new UserException(USER_EXIST_ERROR));

        String loginName = JwtWebUtil.getTokenSubjectExpires(request);
        if (!customerService.verifyLoginAdmin(loginName)) {
            // 非 admin 角色用户操作
            throw new UserException(AUTH_ERROR.getCode(), "只允许管理员重置密码");
        }

        customerService.editPassword(password.getLoginName(), userDefPassword);
        redisLoginService.loginOut(user.getLoginName());
        return ResultVO.successMessage("密码重置成功，请尽快修改默认密码");
    }

    @GetMapping("banned")
    @Operation(summary = "账户封禁")
    @Parameter(name = "id", description = "id集合", required = true)
    @ApiPlatform(platform = PlatformConstant.WEB_ADMIN)
    public ResultVO<String> banned(@RequestParam("id") Long id) {
        customerService.findById(id).ifPresent(user -> {
            if (UserLoginNameDef.SUPPER_USER.contains(user.getLoginName().toLowerCase())) {
                throw new UserException("非法操作，此用户不允许被操作！");
            } else {
                if (user.getStatus() != CustomerStatus.BANNED.getStatus()) {
                    user.setStatus(CustomerStatus.BANNED.getStatus());
                    user.setStatusMark(UserStatusMark.BANNED);
                    customerService.saveOne(user);
                    redisLoginService.loginOut(user.getLoginName());
                }
            }
        });
        return ResultVO.successMessage("账户被封禁");
    }


    @Operation(summary = "编辑用户")
    @PostMapping("edit")
    @ApiPlatform(platform = PlatformConstant.WEB_ADMIN)
    public ResultVO<String> edit(@RequestBody @Valid CustomerEdit edit) {
        customerService.update(edit, SQLOperator.EQ, "id");
        return ResultVO.success();
    }

    @Operation(summary = "查询所有用户")
    @PostMapping("lists")
    public ResultVO<List<CustomerVO>> findAll() {
        List<Customer> finds = customerService.finds();
        return ResultVO.success("查询成功", ListTo.to(CustomerVO.class, finds));
    }


    @Operation(summary = "查询所有正常用户")
    @PostMapping("normalLists")
    public ResultVO<List<CustomerVO>> normalLists() {
        return ResultVO.success("查询成功", ListTo.to(CustomerVO.class, customerService.finds(
                (root, query, builder) ->
                        builder.equal(root.get("status"), CustomerStatus.NORMAL.getStatus())
                , new Sorteds(1, "id"))));
    }

    @Operation(summary = "查询用户-分页")
    @PostMapping("page")
    public ResultPageVO<CustomerVO, JpaPageResult<CustomerVO>> findPage(@RequestBody @Valid CustomerPage page) {
        Page<Customer> customers = customerService.findPage(page, page.getPage());
        JpaPageResult<CustomerVO> pageResult = JpaPageResult.toPage(customers, CustomerVO.class);
        return ResultPageVO.success(pageResult, "查询成功");
    }


    @GetMapping("normal")
    @Operation(summary = "账户恢复正常", description = "待审核的用户不允许这个接口")
    @ApiPlatform(platform = PlatformConstant.WEB_ADMIN)
    @Parameter(name = "id", description = "编号集合", required = true)
    public ResultVO<String> normal(@RequestParam("id") Long id) {
        customerService.findById(id).ifPresent(user -> {
            if (user.getAuditStatus() == 1 || user.getAuditStatus() == 3) {
                log.error("待审核和审核不通过的用户不允许这个接口");
            } else if (user.getStatus() != CustomerStatus.NORMAL.getStatus()) {
                user.setStatus(CustomerStatus.NORMAL.getStatus());
                user.setStatusMark("账户恢复正常");
                customerService.saveOne(user);
                redisLoginService.loginOut(user.getLoginName());
            }
        });
        return ResultVO.successMessage("账户恢复正常");
    }

    @PostMapping("audit")
    @Operation(summary = "审核用户")
    @ApiPlatform(platform = PlatformConstant.WEB_ADMIN)
    public ResultVO<String> audit(@RequestBody @Valid CustomerAudit audit) {
        customerService.findByIds(audit.getId()).forEach(user -> {
            if (user.getStatus() == CustomerStatus.BANNED.getStatus()) {
                user.setStatus(audit.status());
                user.setAuditTime(LocalDateTime.now());
                user.setAuditRemark(audit.getAuditRemark());
                user.setAuditStatus(audit.getAuditStatus());
                if (audit.getAuditStatus() == 3) {
                    user.setStatusMark(UserStatusMark.AUDIT_FAIL);
                } else {
                    user.setStatusMark("审核通过用户");
                }
                customerService.saveOne(user);
                redisLoginService.loginOut(user.getLoginName());
            }
        });
        if (audit.getAuditStatus() == 3) {
            return ResultVO.successMessage("审核驳回，请通知用户规范注册");
        } else {
            return ResultVO.successMessage("审核成功，请通知用户使用");
        }
    }

    @Operation(summary = "用户信息")
    @GetMapping(value = "info")
    public ResultVO<CustomerVO> userInfo(HttpServletRequest request) {
        String tokenSubjectExpires = JwtWebUtil.getTokenSubjectExpires(request);
        Optional<Customer> byLoginName = customerService.findByLoginName(tokenSubjectExpires);
        return byLoginName.
                map(user -> ResultVO.success(SerializableBean.to2(user, CustomerVO.class)))
                .orElseGet(() -> ResultVO.failMessage("获取用户失败，请尝试重新登录"));
    }

    @Operation(summary = "管理员用户集合")
    @PostMapping("admins")
    public ResultVO<List<CustomerVO>> findAdminAll() {
        List<RoleCustomer> admins = roleCustomerService.finds("roleCode", SQLOperator.EQ, "admin");
        if (admins.isEmpty()) {
            return ResultVO.failMessage("暂无管理员，请设置");
        }
        List<Long> userIds = admins.stream().map(RoleCustomer::getUserId).collect(Collectors.toList());
        List<Customer> finds = customerService.findByIds(userIds);
        return ResultVO.success("查询成功", ListTo.to(CustomerVO.class, finds));
    }


    @Operation(summary = "删除")
    @GetMapping("delete")
    @Parameter(name = "id", description = "id", required = true)
    public ResultVO<String> delete(@RequestParam("id") Long id) {
        customerService.deleteEq("id", id);
        return ResultVO.success();
    }

    @GetMapping("roles")
    @Operation(summary = "查询用户拥有的角色")
    @ApiOperationSupport(order = 23)
    @ApiPlatform(platform = PlatformConstant.WEB_ADMIN)
    public ResultVO<List<Role>> findRoleByUser(Long userId) {
        List<String> userRoles = roleCustomerService.getRoleCodeByUserId(userId);
        List<Role> roles = roleService.findByCode(userRoles);
        return ResultVO.success("查询成功", roles);
    }

    @PostMapping("/assign/roles")
    @Operation(summary = "分配角色", description = "给用户新增角色")
    @ApiOperationSupport(order = 23)
    @ApiPlatform(platform = PlatformConstant.WEB_ADMIN)
    public ResultVO<List<Role>> assignRole(@RequestBody @Valid RoleUserSetting roleUser, HttpServletRequest request) {
        LoginJwtExtendInfo userInfo = userOptRoleVerify(roleUser, request);
        Optional<RoleCustomer> exist = roleCustomerService.findByRoleAndUser(roleUser.getRoleCode(), roleUser.getUserId());
        if (exist.isPresent()) {
            return ResultVO.failMessage("当前角色已存在请勿重复添加");
        }

        RoleCustomer roleUserBean = new RoleCustomer();
        roleUserBean.setRoleCode(roleUser.getRoleCode());
        roleUserBean.setUserId(roleUser.getUserId());
        roleCustomerService.saveOne(roleUserBean);
        redisLoginService.loginOut(userInfo.getLoginName());
        return ResultVO.successMessage("分配角色成功");
    }

    @PostMapping("/cancel/roles")
    @Operation(summary = "取消用户的角色")
    @ApiOperationSupport(order = 23)
    @ApiPlatform(platform = PlatformConstant.WEB_ADMIN)
    public ResultVO<List<Role>> cancelRole(@RequestBody @Valid RoleUserSetting roleUser, HttpServletRequest request) {
        LoginJwtExtendInfo userInfo = userOptRoleVerify(roleUser, request);
        roleCustomerService.findByRoleAndUser(roleUser.getRoleCode(), roleUser.getUserId()).ifPresent(r -> {
            roleCustomerService.getJpaBasicsDao().delete(r);
            redisLoginService.loginOut(userInfo.getLoginName());
        });
        return ResultVO.successMessage("取消角色成功");
    }


    /**
     * 操作角色的公共验证
     *
     * @param roleUser RoleUser
     * @param request  HttpServletRequest
     * @return LoginJwtExtendInfo
     */
    @NotNull
    private LoginJwtExtendInfo userOptRoleVerify(RoleUserSetting roleUser, HttpServletRequest request) {
        LoginJwtExtendInfo userInfo = UserUtil.getUserInfo(request);
        if (userInfo.getUserId().equalsIgnoreCase(roleUser.getUserId() + "")) {
            throw new UserException(AUTH_ERROR.getCode(), "请不要改变自己的角色");
        }
        // 当前拥有的权限
        List<String> existRoles = roleCustomerService.getRoleCodeByUserId(Long.valueOf(userInfo.getUserId()));
        // 当前被设置的角色为 内置的管理时，检查用户是否有权限设置

        if (roleUser.getRoleCode().equalsIgnoreCase(RoleDef.USER_ADMIN)
                && existRoles.stream().noneMatch(m -> m.equalsIgnoreCase(RoleDef.USER_ADMIN))) {
            // 非 admin 角色用户操作
            throw new UserException(AUTH_ERROR.getCode(), "非管理员无法对管理员角色进行操作");
        }
        return userInfo;
    }

}
